import {LibraryItem, LibraryType} from "./ElementLibraryTypes";
import {elementLibraryCmd} from "../../../../Command/ElementLibrary/ElementLibraryCommand";
import {eventBus} from "../../../../eventbus/eventbus";
import {language} from "../../../../util/Language";
import ModalContainerMixin  from "../../../BaseComponents/BaseModal/JS/ModalContainerMixin";
/**
 * 要素库状态管理
 * 可以统一调度插入的库对象
 * 要素库独立于模型库、注解库、材质库、标准件库，仅仅做外层兼容
 * TODO: 后续可以将更多库添加到要素库，无需变更原有的代码，仅需要实现对应的方法即可
 */
class ElementLibraryManager {
  /**
   *  历史记录的解析器
   *  所有的历史记录解析都靠这个
   * @type {Map<HistoryType, Function>}
   */
  historyRecordParser = new Map();

  /**
   * 历史记录后退最大保存步数(historyBackMaxStep-1)
   * 为什么会减去1，因为第一个是为最后一个记录做回退而准备的
   * @type {number}
   */
  historyBackMaxStep = 11;
  /**
   * 前进的最大历史记录步长
   * 前进和回退的构造存在区别，需要单独分开
   * @type {number}
   */
  historyGoMaxStep = 10;

  /**
   * 后退历史记录
   * @type {Array<HistoryRecord>}
   */
  afterHistoryRecord = [];

  /**
   * 前进历史记录
   * @type {Array<HistoryRecord>}
   */
  beforeHistoryRecord = []

  /**
   * 加载记录
   * @type {Map<string, boolean>}
   */
  loadRecord = new Map();

  /**
   * 要素库拥有父子关系的结构表
   * @type {LibraryItem[]}
   */
  elementLibraryItems = [];
  /**
   * 要素库项展开列表
   * 一维的，用于快速查找
   * @type {Map<string, LibraryItem>}
   */
  elementLibraryItemMap = new Map();

  /**
   * 插入的库列表
   * @type {Map<ElementLibraryType,IElementLibraryRepository>}
   */
  repository = new Map();

  /**
   * 临时选中的项，为了给命令传递数据
   * @type {LibraryItem}
   * @private
   */
  _tempSelectedItem = null;
  /**
   * 要素库命令面板是否激活
   * @type {boolean}
   */
  cmdPanelIsActive = false;
  /**
   * 要素库右侧栏是否激活
   * @type {boolean}
   */
  rightNavigationPanelIsActive = false;

  /**
   * 当前正在创建的文件夹
   * @type {LibraryItem | null}
   */
  curCreateItem = null;

  /**
   * 正在插入的项目
   * @type {LibraryItem|null}
   */
  curInsertItem = null;

  /**
   * 正在加载某些文件夹
   * @private
   * @type {boolean}
   */
  _loading = false;

  currentDocumentType = null;
  /**
   * @param status {boolean}
   */
  set loading(status) {
    this._loading = status;
    // this.loadSubject.next(status);
  }

  get permitSync() {
    return this.rightNavigationPanelIsActive && this.cmdPanelIsActive;
  }

  constructor() {
    // 初始化状态
    this.initState();
    // this.initElementLibrary();
  }
  /**
   * 初始化要素库状态
   */
  initState() {
    this.elementLibraryItems = [];
    this.loadRecord = new Map();
    this.elementLibraryItemMap = new Map();
    this.repository = new Map();
    this.historyRecordParser = new Map();
  }

  initElementLibrary(cb) {
    let params = {
      cmdName: "getAllElementLibraryFolder",
    }
    elementLibraryCmd.executeElementLibraryCommand(params, res => {
      res.forEach(data => {
        if (!this.elementLibraryItemMap.has(data.id)) {
          let item = new LibraryItem(data);
          this.elementLibraryItemMap.set(item.id, item);
          if (data.folderCode === "0") {
            this.elementLibraryItems.push(item);
          } else {
            let parentItem = this.elementLibraryItemMap.get(data.folderCode);
            item.parent = parentItem;
            parentItem.children.push(item);
          }
        }
      })
      cb(this.elementLibraryItems);
    })
  }

  /**
   * 同步命令和侧边栏的选中
   * 用于提升用户在命令中选中文件夹的使用体验
   * @param item {LibraryItem} 同步项
   * @param from {"cmd"|"library"} 同步来源
   */
  syncCmdAndRightNavigationSelected(item, from) {
    // 如果同步请求来自于命令
    if (from === "cmd") {
      this.openAllParentFolder(item, "library");
      eventBus.emit("ElementLibrarySyncSelectLib", item);
    } else {
      // 同步请求来自与侧边面板
      this.openAllParentFolder(item, "cmd");
    }
  }

  /**
   * 展开所有上级文件夹
   * @param item {LibraryItem} 选中项
   * @param mode {"library" | "cmd"}
   */
  openAllParentFolder(item, mode) {
    if (!item) return;
    const eventName = mode === "library" ? "unfoldElementLibraryFolder" : "cmdUnfoldElementLibraryFolder";
    let targetId = item.id;
    let parent = item;
    let folderList = [];
    while (parent.folderCode !== "0") {
      folderList.push(parent.folderCode);
      parent = this.elementLibraryItemMap.get(parent.folderCode);
    }
    // 这三个参数分别是展开的路径(ID集合)，设置路径上文件夹的状态，选中项的Id，
    eventBus.emit(eventName, folderList, true, targetId);
  }

  /**
   * 创建要素库文件夹
   *
   */
  createFolder(folderName, folderCode,cb) {
    // 如果不选择文件夹则在顶层创建新的项目
    if (!folderCode) {
      folderCode = "0";
    }
    const item = this.buildItem(folderName, folderCode, LibraryType.FOLDER);
    let params = {
      cmdName: "createElementLibrary",
      item: item,
    }
    elementLibraryCmd.executeElementLibraryCommand(params, res => {
      if (res?.code === 200) {
        this.addAppendChildren(folderCode, new LibraryItem(res?.data));
        cb();
      }
    })
  }
  /**
   * 预创建文件夹执行的操作
   * @param parent {LibraryItem}
   */
  preCreateFolder(parent) {
    // 如果parent存在，则证明传递了LibraryItem，否则使用根目录
    let childrenList = parent && parent.id !== '-1' ? parent.children : this.elementLibraryItems;
    let folderCode = parent && parent.id !== '-1' ? parent.id : "0";
    // 使用内置的通用方法生成下一个文件的名称
    const compareList = childrenList.map(item => item.name);
    let folderName = this.generateNameByNo(language.getMessage("message.ElementLibrary.newFolder"), compareList);
    // 临时的虚拟知识项
    this.curCreateItem = new LibraryItem({
      id: "-1",
      folderCode: folderCode,
      elementLibraryType: LibraryType.FOLDER,
      name: folderName,
    });
    this.addAppendChildren(folderCode, this.curCreateItem)
  }
  /**
   * 根据前缀生成固定的名称
   * 注:该方法是通用的,主要用于重命名、创建文件夹时生成临时的文件名称
   * 非必要情况下不要更改此段代码，可能会导致新建文件夹生成的名称错误
   * @param prefix {string} 生成前缀
   * @param compareList {Array<string>} 对比列表(项目名称)
   * @return {string} 最后产生的结果
   */
  generateNameByNo(prefix, compareList) {
    /**
     * 防止出现新建文件夹(1)这种情况，括号无法在下方的new RegExp环节转义，从而导致正则匹配失败，该代码用于防止这种情况
     * prefix保持不变，因为下方还要使用
     * @type {string}
     */
    let transformPrefix = prefix.replaceAll("(", "\\(")
      .replaceAll(")", "\\)");

    let regexp = new RegExp(`^${transformPrefix}(\\((?<no>\\d+)\\))?$`);
    /**
     * 使用流的形式将不符合规则的子项排除在外
     * 使用map和正则命名规则将所有的序列号映射为一个新的序号数组
     * 使用sort对数组进行升序排序(涉及到后面缝隙检查的问题)
     */
    let noList = compareList
      .filter(name => regexp.test(name))
      .map(name => {
        let noStr = regexp.exec(name).groups?.no?.replace("(", "").replace(")", "");
        let no = parseInt(noStr);
        return isNaN(no) ? 0 : no;
      })
      .sort((a, b) => a - b);
    /**
     * 下方是对于空隙值的检查流程
     */
    let interspaceNo = -1;
    // 比对下标,查看是否存在空隙(例如:有文件1和文件3，但唯独没有文件2)
    noList.some((no, index) => {
      // 0比较特殊排除掉
      if (no !== index) {
        // 发现空隙，填补空隙
        interspaceNo = index;
      }
      return no !== index;
    });
    if (noList.length === 0 || interspaceNo === 0) {
      return prefix;
    }
    // 如果没有空隙
    if (interspaceNo === -1) {
      // 如果两者不匹配，则prefix可能是XX(1)这种形式
      return `${prefix}(${noList.length})`;
    }

    return `${prefix}(${interspaceNo})`;
  }

  /**
   * 添加子项到文件夹中
   * @param folderCode {string} 文件夹Code
   * @param child {LibraryItem} 子项
   */
  addAppendChildren(folderCode, child) {
    // 已经添加到列表，则无需再次添加
    if (this.elementLibraryItemMap.has(child.id)) return;
    // 如果在根目录创建文件夹
    if (folderCode === "0") {
      // 根目录仅能添加文件夹
      if (child.libraryType !== LibraryType.FOLDER) return;
      this.elementLibraryItems.push(child);
    } else {
      // 对于非根目录
      let folder = this.elementLibraryItemMap.get(folderCode);
      // 对于文件夹而言，需要填充到非文件夹项的最前方
      if (child.libraryType === LibraryType.FOLDER) {
        let folderLength = folder.children.filter(item => item.libraryType === LibraryType.FOLDER).length;
        folder.children.splice(folderLength, 0, child);
      } else {
        folder.children.push(child);
      }
    }
    this.elementLibraryItemMap.set(child.id, child);
  }

  /**
   * 加载子项
   * 如果在加载子项后你有一些后续的操作，可以借助返回的Promise对象进行链式操作
   * @public
   * @param folderCode {string}
   * @return {Promise}
   */
  loadChildrenItem(folderCode, cb) {
    // 提前存储记录防止网络延迟
    this.loadRecord.set(folderCode, true);
    this.loading = true;
    let params = {
      cmdName: "loadChildrenItem",
      id: folderCode,
    }
    elementLibraryCmd.executeElementLibraryCommand(params, res => {
      this.currentDocumentType = res?.currentDocumentType;
      // 解析每一个子项，并添加状态管理中存储
      res?.elementLibraryItems.forEach(param => {
        let item = new LibraryItem(param);
        this.addAppendChildren(folderCode, item);
      })
      this.loading = false;
      cb();
    })
  }

  /**
   * 是否已经加载文件夹
   * 这个方法用于知识库内部判定某些文件夹是否被重复加载，在外部仍然可以调用并判断你要操作的文件夹是否加载
   * 注意，这里的加载并不是指目标下文件夹的加载，由于之前改版过一次(知识库会在初始化时加载所有的文件夹目录)
   * 这里的加载项是指目标文件夹下的知识项(非文件夹类型的项目)是否被加载
   * @param folderCode {string}
   * @return {boolean}
   */
  hasLoad(folderCode) {
    return this.loadRecord.has(folderCode);
  }
  /**
   * 获取父文件夹
   * @param item {LibraryItem}
   * @return {LibraryItem}
   */
  getParentByItem(item) {
    return this.elementLibraryItemMap.get(item?.folderCode) ?? null
  }
  /**
   * 重命名
   * 这个方法仅仅用于重命名前端本地的数据缓存，无法变更真正的后台数据
   * 如果需要变更后台数据，请参考[知识库/api]下面的文件
   * @param id {string} Item id
   * @param newName {string} 新的名称
   */
  renameItem(id, newName) {
    let item = this.elementLibraryItemMap.get(id);
    if (!item) return;
    item.name = newName;
  }

  /**
   * 创建文件夹后的执行操作
   */
  afterCreateFolder() {
    if (!this.curCreateItem) return;
    this.removeChildren(this.curCreateItem.id);
    this.curCreateItem = null;
  }

  /**
   * 删除要素库(本地)
   * @param id {string}
   */
  removeChildren(id) {
    let item = this.elementLibraryItemMap.get(id);
    if (!item) return;
    // 删除展平后的表
    this.elementLibraryItemMap.delete(id);
    if (item.folderCode === "0") {
      this.elementLibraryItems = this.elementLibraryItems.filter(item => item.id !== id);
    }
    // 从父文件夹中移除
    let parent = this.elementLibraryItemMap.get(item.folderCode);
    if (!parent) return;
    parent.children = parent.children.filter(c => c.id !== item.id);
  }
  /**
   * 根据项Id获取知识项
   * @param id {string}
   * @return {LibraryItem}
   */
  getItemById(id) {
    return this.elementLibraryItemMap.get(id) ?? null;
  }
  /**
   * 构建LibraryItem
   * @private
   * @param name {string}
   * @param folderCode {string}
   * @param type {LibraryType}
   * @param isSystem
   * @param param {string}
   * @return {LibraryItem}
   */
  buildItem(name, folderCode, type, isSystem = false, param = "") {
    let item = new LibraryItem();
    item.name = name;
    item.libraryType = type;
    item.param = param;
    item.folderCode = folderCode;
    item.isSystem = isSystem;

    return item;
  }
  /**
   * 删除要素项
   * 该方法会同时清除本地和后台的数据，调用时请注意
   * @param id {string}
   */
  deleteItem(id) {
    let isConfirm = confirm(language.getMessage("message.ElementLibrary.confirmDelTip"));
    if (isConfirm) {
      let params = {
        cmdName: "deleteElementLibrary",
        id: id,
      }
      elementLibraryCmd.executeElementLibraryCommand(params, res => {
        eventBus.emit("deleteElementLibraryFolder", id);
        this.removeChildren(id);
      });
    }
  }

  getParentFolders(item, parentFolders = []) {
    if (item && item.folderCode !== '0') {
      let parentFolder = this.getItemById(item.folderCode);
      parentFolders.push(parentFolder);
      this.getParentFolders(parentFolder, parentFolders);
    }
    return parentFolders;
  }

  /**
   * 将要素项插入的工作空间(文件夹除外)
   * 该方法会获取要素库中已经加载的库对象，并调用这些库对项的实现方法
   * @param item {LibraryItem}
   * @param drag {boolean} [default=false] 是否为拖拽
   */
  insert(item, drag = false) {
    if (item?.param?.parametricTableId) {//参数化构件
      let params = {
        cmdName: 'openToolMenu',
        toolName: 'ProgramPlugin-selectParameterInsert',
        item:item,
      }
      elementLibraryCmd.executeElementLibraryCommand(params, res => {});
    } else {//非参模型
      let params = {
        modalParam:{
          insertPos: new THREE.Vector3(),
        },
        elementItem:item,
      }
      elementLibraryCmd.executeInsertLibPartCommand(params,result =>{})
    }
    // eventBus.emit("openModal", "knowledgeSelectParametric", {
    //   knowledgeId: item.id,
    //   templateId: item.param.templateId,
    //   insertPos: new THREE.Vector3(),
    //   parametricTableId: item.param.parametricTableId
    // })
  }
  /**
   * 获取文件的路径信息
   * @param item {LibraryItem}
   * @return string
   */
  findFilePath(item) {
    /**
     * 路径临时存储的数组
     * @type {Array<string>}
     */
    let pathArr = [];
    let lastItem = item;
    while (lastItem) {
      pathArr.push(lastItem.name);
      lastItem = this.elementLibraryItemMap.get(lastItem.folderCode);
    }

    /**
     * 整理好的路径 XX/XX/XX
     */
    return pathArr.reverse().join("/");
  }
}


const elementLibraryManager = new ElementLibraryManager();

export default elementLibraryManager;